#include <cassert>
#include <algorithm>

#include "preprocess_helper.h"


struct box
{
    struct { uint32_t x, y, z; } min;
    struct { uint32_t x, y, z; } max;
};


static box findBox(const std::vector<String3DPoint>& str_points)
{
    box box;
#define FILL_BOX_AT(coord) \
do { \
    auto pair = std::minmax_element(str_points.begin(), str_points.end(), \
        [](const String3DPoint& str_point_1, const String3DPoint& str_point_2) \
        { return str_point_1.coord.int_part < str_point_2.coord.int_part; } \
    ); \
    box.min.coord = pair.first->coord.int_part; \
    box.max.coord = pair.second->coord.int_part; \
} while(0)

    FILL_BOX_AT(x);
    FILL_BOX_AT(y);
    FILL_BOX_AT(z);

    return box;
#undef FILL_BOX_AT
}


PreprocessInfo GrabPreprocessInfo(const std::vector<String3DPoint>& str_points)
{
#define FILL_SHIFT_AT(box_found, coord, preprocess_info) \
do { \
    preprocess_info.shift.coord = box_found.min.coord; \
} while(0)

#define FILL_SCALE_AT(box_found, coord, preprocess_info) \
do { \
    preprocess_info.scale.coord = box_found.max.coord - box_found.min.coord; \
} while(0)

    PreprocessInfo preprocess_info = {};
    box box_found = findBox(str_points);

    FILL_SHIFT_AT(box_found, x, preprocess_info);
    FILL_SHIFT_AT(box_found, y, preprocess_info);
    FILL_SHIFT_AT(box_found, z, preprocess_info);

    FILL_SCALE_AT(box_found, x, preprocess_info);
    FILL_SCALE_AT(box_found, y, preprocess_info);
    FILL_SCALE_AT(box_found, z, preprocess_info);

    return preprocess_info;

#undef FILL_SHIFT_AT
#undef FILL_SCALE_AT
}


static void convertToDoublePoint(const std::vector<String3DPoint>& str_points, std::vector<BaseGeo::Point>& points)
{
    for (String3DPoint str_point : str_points) {
        points.push_back(str_point.ToDoublePoint());
    }
}


static void shiftPoints(const struct_u32_xyz& shift, std::vector<String3DPoint>& str_points)
{
    for (String3DPoint& str_point : str_points) {
        str_point.x.int_part -= shift.x;
        str_point.y.int_part -= shift.y;
        str_point.z.int_part -= shift.z;
    }
}


static void normalizePoints(const struct_u32_xyz& scale, std::vector<BaseGeo::Point>& points)
{
    for (BaseGeo::Point& point : points) {
        point.x /= scale.x;
        point.y /= scale.y;
        point.z /= scale.z;
    }
}


PreprocessedPointsInfo PreprocessPoints(std::vector<String3DPoint> str_points, const PreprocessInfo& preprocess_info)
{
    PreprocessedPointsInfo preprocessed_points_info = {
        .preprocess_info = preprocess_info
    };

    shiftPoints(preprocess_info.shift, str_points);
    convertToDoublePoint(str_points, preprocessed_points_info.points);
    normalizePoints(preprocess_info.scale, preprocessed_points_info.points);

    return preprocessed_points_info;
}


BaseGeo::Point PreprocessPoint(String3DPoint str_point, const PreprocessInfo& preprocess_info)
{
    str_point.x.int_part -= preprocess_info.shift.x;
    str_point.y.int_part -= preprocess_info.shift.y;
    str_point.z.int_part -= preprocess_info.shift.z;

    BaseGeo::Point point = str_point.ToDoublePoint();

    point.x /= preprocess_info.scale.x;
    point.y /= preprocess_info.scale.y;
    point.z /= preprocess_info.scale.z;

    return point;
}


String3DPoint InversePreprocessedPoint(BaseGeo::Point point, const PreprocessInfo& preprocess_info)
{
    point.x *= preprocess_info.scale.x;
    point.y *= preprocess_info.scale.y;
    point.z *= preprocess_info.scale.z;

    String3DPoint str_point = String3DPoint(point);

    str_point.x.int_part += preprocess_info.shift.x;
    str_point.y.int_part += preprocess_info.shift.y;
    str_point.z.int_part += preprocess_info.shift.z;

    return str_point;
}